Skip to content

Conversation

@Harshdev098
Copy link

Have added in_memory store for testing purpose.
We can edit config file to use specific store either postgresql or memory

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Oct 12, 2025

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@Harshdev098
Copy link
Author

Harshdev098 commented Oct 12, 2025

Hey @tnull @tankyleo Can you please review it

@tnull tnull self-requested a review October 13, 2025 07:10
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this!

Generally goes into the right direction, but we def. need to avoid re-allocating everything on every operation.

@Harshdev098 Harshdev098 force-pushed the memory_store branch 2 times, most recently from 4980a75 to 25d57e3 Compare October 14, 2025 06:16
@Harshdev098 Harshdev098 requested a review from tnull October 14, 2025 06:17
@Harshdev098
Copy link
Author

@tnull Have done the required changes

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks much better, but I think we still need to handle global_version properly, even if we're currently not using it client-side.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tnull @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment, will take another look once @tankyleo also had a chance to do a review round here.

@Harshdev098
Copy link
Author

@tankyleo Can you please review it!

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Contributor

@tankyleo tankyleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay !

@Harshdev098
Copy link
Author

@tankyleo Have done with the required changes! Can you please review it

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing integration with LDK Node locally I found that the tests are currently failing. I now opened #62 to add LDK Node integration tests to our CI here. It would be great if that could land first, and we could also add a CI job for the in-memory store as part of this PR then, ensuring the implementation actually works as expected.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@tnull
Copy link
Contributor

tnull commented Oct 31, 2025

@Harshdev098 Please rebase now that #62 landed to make use of the new CI checks here.

@Harshdev098 Harshdev098 force-pushed the memory_store branch 2 times, most recently from 51a791d to 0168750 Compare November 8, 2025 05:48
@Harshdev098
Copy link
Author

Hey @tnull Have updated the code and the unit test are passing against the ldk node tests but didn't understand what is the cause of integration failures

@Harshdev098 Harshdev098 force-pushed the memory_store branch 3 times, most recently from 92bd1e3 to bd3eca4 Compare November 9, 2025 03:41
@Harshdev098
Copy link
Author

Hey @tnull @tankyleo Have tested it locally, the test are working correctly now, don't know why its stuck in CI! Can you please review it

@Harshdev098 Harshdev098 requested a review from tnull November 9, 2025 03:53
@Harshdev098 Harshdev098 force-pushed the memory_store branch 2 times, most recently from 9ec0e79 to 45fecd1 Compare November 9, 2025 10:18
@ldk-reviews-bot
Copy link

🔔 7th Reminder

Hey @tnull @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 8th Reminder

Hey @tnull @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Contributor

@tankyleo tankyleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay again I have cleared all the other priorities, this is now top priority :) Here are some comments, mostly on the types we pass to the different functions.

Will continue review tomorrow 100% ! Thank you again.

ErrorKind::Other,
format!("Failed to drop database {}: {}", db_name, e),
)
Error::new(ErrorKind::Other, format!("Failed to drop database {}: {}", db_name, e))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry the formats in this file were done in a CI-specific PR we merged, go ahead and rebase and drop them thank you :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Harshdev098 sorry i take it back, don't rebase to a new commit just yet :) if you can just drop these changes, keep the same base commit on main, will rebase to a newer base commit as necessary thank you

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh sorry @tankyleo , but I have done it already, my branch is now up to date with main

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Harshdev098 want to make sure you know about git reflog ? Would have allowed you to revert it back easily before pushing but all good now !

Comment on lines 273 to 277
let vss_delete_records: Vec<VssDbRecord> = request
.delete_items
.into_iter()
.map(|kv| build_vss_record(user_token.clone(), store_id.clone(), kv))
.collect();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also do not need to build VssDbRecords for the delete_items, so let's delete those.

ErrorKind::Other,
format!("Failed to drop database {}: {}", db_name, e),
)
Error::new(ErrorKind::Other, format!("Failed to drop database {}: {}", db_name, e))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh sorry @tankyleo , but I have done it already, my branch is now up to date with main

@Harshdev098
Copy link
Author

@tankyleo Have updated the code

Copy link
Contributor

@tankyleo tankyleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments focusing on in_memory_store thank you


/// In-memory implementation of the VSS Store.
pub struct InMemoryBackendImpl {
store: Arc<RwLock<HashMap<String, VssDbRecord>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you share why you picked an RwLock here ? I always default to Mutex unless it will be read-heavy / have good reasons to use RwLock.

VSS is rather even-handed / write-heavy, so my gut instinct is to go for Mutex.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually have used Mutex earlier but have changed to RwLock for debugging the integration test that was failing!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good ok would you consider switching back to Mutex ? or are there some bugs that come up specifically when using Mutex ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching back to Mutex, it was deadlock issue at that time!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I found that testing the ldk node integration test against memory_store I need to restart the server to test after every updation, because I think starting the integration test it needs fresh data but it gives error when runned multiple times without restarting the server, which can make bad experience as a developer @tankyleo!?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Harshdev098 does this have to do with the choice between Mutex and RwLock ? I'll look into this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, you can reproduce it by just running the ldk node integration test multiple times with the same server without restarting it. I think it could be a problem with test because every time we run the test and its failing while setting up the first node and it's giving on Error value
ReadFailed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you had success with passing the ldk integration test against in_memory just once ? I've passed against postgres all good, but in_memory_store seems to hang.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh sorry, it was my fault but I have updated the pr with the required changes, and yah I have also suffered of this hang but underestimated it because its not happening regularly like with the same code sometimes, it's passes the integration test and sometimes just stuck to it, the same was happening with ldk test against postgres too in CI @tankyleo @tnull

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh sorry, it was my fault but I have updated the pr with the required changes, and yah I have also suffered of this hang but underestimated it because its not happening regularly like with the same code sometimes, it's passes the integration test and sometimes just stuck to it, the same was happening with ldk test against postgres too in CI @tankyleo @tnull

Yes, this might happen on current LDK Node main. It should be fixed as soon as lightningdevkit/ldk-node#627 lands, which should happen shortly/in the few days.


/// In-memory implementation of the VSS Store.
pub struct InMemoryBackendImpl {
store: Arc<RwLock<HashMap<String, VssDbRecord>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we use a BTreeMap here to avoid having to resort all the keys on every subsequent call to list_key_versions further down below ?

if args.len() != 2 {
eprintln!("Usage: {} <config-file-path>", args[0]);
if args.len() < 2 {
eprintln!("Usage: {} <config-file-path> [--in-memory]", args[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this specific flag requested by a user ? I'd be in favor of deleting it, and keeping the in_memory configuration to a single spot, as we currently have it in the configuration file.

But let me know what you think, curious to hear your thoughts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added --in-memory to make it easy for external projects like Fedimint to run integration tests with our VSS server

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good ok and the configuration file would be too much of a hassle for them ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, exactly

Copy link
Contributor

@tankyleo tankyleo Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we could change the "default" database to "in_memory" store so people can clone the repository, and run CI directly right ?

Also another question: could Fedimint just run against postgres ? We've done some work in the past to make setting that up much easier, as close to "just run it". Thank you for the context. You can see our own build-and-deploy-rust.yml in this repo runs test against a postgres service without too much trouble.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yah they can run it in postgres but the in-memory simplifies their devimint CI like no DB service needed, just spawn the binary,
And the in-memory flag, it's low-effort and keeps config as the single source while enabling overrides. I don't think to make the in_memory the default one!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok sound sgood let's keep things as you've got them now thank you

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

pub(crate) struct ServerConfig {
pub(crate) host: String,
pub(crate) port: u16,
pub(crate) store_type: String, // "postgresql" or "in_memory"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider using a StoreType enum instead of String?

#[derive(Deserialize)]
pub(crate) enum StoreType {
    #[serde(rename = "postgres")]
    Postgres,
    #[serde(rename = "in-memory")]
    InMemory,
}

We'd get:

  1. Better type safety because invalid values would be rejected when we parse the configuration parameters and not latter downstream.
  2. A self-documenting type that clearly defines valid options without requiring inline comments.
  3. Exhaustive matching that handles the variants only without needing to handle the wildcard _ arm as is currently done with.
    _ => {
    eprintln!("Invalid backend_type: {}. Must be 'postgres' or 'in_memory'", config.server_config.store_type);
    std::process::exit(1);
    },
  4. Refactoring safety when/if we add new store types as this will produce compile errors at all usage sites.

The load_config function remains unchanged since serde handles the deserialization automatically.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ofcourse, better way to handle that! Thanks.

},
_ => {
eprintln!("Invalid backend_type: {}. Must be 'postgres' or 'in_memory'", config.server_config.store_type);
std::process::exit(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As already mentioned, I don't think we should have configuration-related failures here.

// Override the `store_type` if --in-memory flag passed
if use_in_memory {
println!("Overriding backend type: using in-memory backend (via --in-memory flag)");
config.server_config.store_type = "in_memory".to_string();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to be modified if you accept the StoreType enum recommendation.

Comment on lines 15 to 18
pub mod in_memory_store;
mod migrations;
/// Contains [PostgreSQL](https://www.postgresql.org/) based backend implementation for VSS.
pub mod postgres_store;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Would you consider grouping modules by visibility, i.e. public together? This is just a style preference so up to you to decide.

@@ -0,0 +1,414 @@
use crate::postgres_store::{
VssDbRecord, LIST_KEY_VERSIONS_MAX_PAGE_SIZE, MAX_PUT_REQUEST_ITEM_COUNT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should VssDbRecord remain in postgres_store? Currently, in_memory_store importing from postgres_store creates a false dependency and suggests the type is PostgreSQL-specific rather than a shared abstraction. Would you consider moving it to a models.rs file (which would also provide a home for other shared types)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Contributor

@tankyleo tankyleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also rephrase your commit messages to use the imperative form ? See https://cbea.ms/git-commit/

Err(VssError::NoSuchKeyError("Requested key not found.".to_string()))
};

result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need for the extra result variable here, just return from the if statement directly

let storage_prefix = format!("{}#{}#", user_token, store_id);
let prefix_len = storage_prefix.len();

let mut all_items: Vec<KeyValue> = guard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did not address the earlier feedback, we are still making an extra allocation here when we only need one

})
.collect();

all_items.sort_by(|a, b| a.key.cmp(&b.key));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again as I mentioned further above, we now don't need this sort here let's delete it


let mut all_items: Vec<KeyValue> = guard
.iter()
.filter(|(storage_key, _)| storage_key.starts_with(&storage_prefix))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here let's filter out the keys that do not match the prefix as well as the global version key

global_version = Some(self.get_current_global_version(&guard, &user_token, &store_id));
}

let storage_prefix = format!("{}#{}#", user_token, store_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's set this to build_storage_key(&user_token, &store_id, &key_prefix); so that we can do a single filter call to filter out all the keys.

let mut all_items: Vec<KeyValue> = guard
.iter()
.filter(|(storage_key, _)| storage_key.starts_with(&storage_prefix))
.filter_map(|(storage_key, record)| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not do any filtering in this function, just map


let mut all_items: Vec<KeyValue> = guard
.iter()
.filter(|(storage_key, _)| storage_key.starts_with(&storage_prefix))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this call, we can do .skip(offset).take(limit).map...

all_items.sort_by(|a, b| a.key.cmp(&b.key));

let page_items: Vec<KeyValue> =
all_items.iter().skip(offset).take(limit).cloned().collect();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned earlier, this skip(offset).take(limit) can be folded into the first iterator chain.

[server_config]
host = "127.0.0.1"
port = 8080
store_type = "postgres" # "postgres" for using postgresql and "in_memory" for testing purposes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in_memory is not valid any longer

use chrono::Utc;

/// A record stored in the VSS database.
pub struct VssDbRecord {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's delete all the pub here this is not part of the public API, and let's move this to the tail end of src/lib.rs, this is a better spot for this stuff - we can delete model.rs

let limit = std::cmp::min(page_size, LIST_KEY_VERSIONS_MAX_PAGE_SIZE) as usize;

let offset: usize =
request.page_token.as_ref().and_then(|s| s.parse::<usize>().ok()).unwrap_or(0);
Copy link
Contributor

@tankyleo tankyleo Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we cannot parse the token, that is an error - currently we accept it and set offset to 0.

);
println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, db_name);
let store: Arc<dyn KvStore> = match config.server_config.store_type {
StoreType::Postgres => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please make sure you use tabs, not spaces

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants